#include "rpc/net/socket.h"
#include "rpc/net/buffer.h"
#include "rpc/net/socket_opt.h"

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/tcp.h>

using namespace rpc;
namespace sock {

int32 CreateSocket() {
  int32 ret = ::socket(AF_INET, SOCK_STREAM, 0);
  if (ret == -1) {
    PLOG(WARNING)<< "socket error";
    return -1;
  }

  VLOG(1) << "create socket, fd: " << ret;
  return ret;
}

void Close(int sockfd) {
  if (sockfd != -1) {
    ::close(sockfd);
    VLOG(1) << "close socket, fd: " << sockfd;
  }
}

bool Bind(rpc::Socket* s, const rpc::Addr& addr) {
  if (!TrunOnOpt(s, SOL_SOCKET, SO_REUSEADDR)) {
    return false;
  }
  int32 ret = ::bind(s->Fd(), (struct sockaddr*) &addr.addr, sizeof(addr.addr));
  if (ret == -1) {
    PLOG(WARNING)<< "socket bind error";
    return false;
  }

  if (!TrunOnOpt(s, SOL_SOCKET, SO_KEEPALIVE)) {
    return false;
  }
  if (!TrunOnOpt(s, IPPROTO_TCP, TCP_NODELAY)) {
    return false;
  }
  if (!TrunOnOpt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT)) {
    return false;
  }

  return true;
}

#define BACK_LOG 4096

bool Listen(rpc::Socket* s) {
  CHECK_NOTNULL(s);
  int32 ret = ::listen(s->Fd(), BACK_LOG);
  if (ret == -1) {
    PLOG(WARNING)<< "listen error, fd: " << s->Fd();
    return false;
  }

  return true;
}

int32 Accept(rpc::Socket* s, rpc::Addr* addr) {
  CHECK_NOTNULL(s);
  CHECK_NOTNULL(addr);
  struct sockaddr* accept_addr = NULL;
  socklen_t len = sizeof(struct sockaddr_in);
  if (addr && !addr->empty()) {
    accept_addr = (struct sockaddr*) &addr->addr;
  }
  uint32* addr_len = accept_addr == NULL ? NULL : &len;

  int32 ret = ::accept4(s->Fd(), accept_addr, addr_len,
  SOCK_CLOEXEC | SOCK_NONBLOCK);
  if (ret < 0) {
    PLOG(WARNING)<< "accept4 error, accept fd: " << s->Fd();
    return -1;
  }

  return ret;
}

bool Connect(rpc::Socket* s, const rpc::Addr& addr) {
  int32 ret;
  do {
    ret = ::connect(s->Fd(), (struct sockaddr*) &addr.addr, sizeof(addr.addr));
  } while (ret == -1 && errno == EINTR);

  if (ret == -1) {
    PLOG(WARNING)<< "connect error, fd: " << s->Fd();
    return false;
  }

  return true;
}

bool SetSockOpt(rpc::Socket* s, int32 level, int32 opt, void* val, size_t len) {
  if (s == NULL || s->Fd() < 0 || val == NULL) return false;
  int32 ret = ::setsockopt(s->Fd(), level, opt, val, len);
  if (ret == -1) {
    PLOG(WARNING)<< "setsockopt error";
    return false;
  }

  return true;
}

bool GetSockOpt(rpc::Socket* s, int32 level, int32 opt, void* val,
                uint32* len) {
  if (s == NULL || s->Fd() < 0 || val == NULL || *len < 0) {
    return false;
  }
  int32 ret = ::getsockopt(s->Fd(), level, opt, val, len);
  if (ret == -1) {
    PLOG(WARNING)<< "setsockopt error";
    return false;
  }

  return true;
}

bool TrunOnOpt(rpc::Socket* s, int32 level, int32 opt) {
  int32 val = 1;
  return SetSockOpt(s, level, opt, &val, sizeof(val));
}

bool TrunOffOpt(rpc::Socket* s, int32 level, int32 opt) {
  int32 val = 0;
  return SetSockOpt(s, level, opt, &val, sizeof(val));
}

rpc::Addr GetSockAddr(rpc::Socket* s, bool is_local) {
  rpc::Addr addr;
  int32 ret, len = sizeof(struct sockaddr_in);
  if (!is_local) {
    ret = ::getpeername(s->Fd(), (struct sockaddr*) &addr.addr,
                        (socklen_t *) &len);
  } else {
    ret = ::getsockname(s->Fd(), (struct sockaddr*) &addr.addr,
                        (socklen_t *) &len);
  }

  PCHECK(ret != -1) << "getsockname error";

  addr.ip = IpToStr(addr.addr.sin_addr.s_addr);
  addr.port = ::ntohs(addr.addr.sin_port);

  return addr;
}

ssize_t Recv(const rpc::Socket* sock, rpc::Buffer* buf, size_t len) {
  if (sock != NULL && len > 0) return -1;
  if (buf->WriteableSize() < len) {
    size_t old_size = buf->size();
    buf->Extern(len < old_size ? old_size : len);
  }

  int32 n = ::recv(sock->Fd(), buf->WritePos(), len, 0);
  if (n == -1) {
    PLOG(WARNING)<< "recv error, fd: " << sock->Fd();
    return -1;
  }

  buf->WriteSkip(n);
  return n;
}

ssize_t Recv(const rpc::Socket* sock, u_char* buf, size_t buflen) {
  if (sock != NULL && buflen > 0) return -1;
  int32 n = ::recv(sock->Fd(), buf, buflen, 0);
  if (n == -1) {
    PLOG(WARNING)<< "recv error, fd: " << sock->Fd();
  }

  return n;
}

ssize_t Send(const rpc::Socket* sock, const u_char* buf, size_t buflen) {
  CHECK(sock != NULL && buflen > 0) << "length: " << buflen;
  int32 ret = ::send(sock->Fd(), buf, buflen, 0);
  if (ret == -1) {
    PLOG(WARNING)<< "write sock error, fd: " << sock->Fd();
  }

  return ret;
}

ssize_t Send(const rpc::Socket* sock, rpc::Buffer* buf) {
  if (sock != NULL && buf != NULL) return -1;
  size_t len = buf->ReadableSize();
  int32 ret = ::send(sock->Fd(), buf->Peek(), len, 0);
  if (ret == -1) {
    PLOG(WARNING)<< "send socker error: " << sock->Fd();
    return -1;
  }

  buf->Skip(ret);
  return ret;
}

ssize_t Send(const rpc::Socket* sock, rpc::Buffer* buf, size_t len) {
  if (sock != NULL && buf != NULL) return -1;
  if (len > buf->ReadableSize()) {
    len = buf->ReadableSize();
  }
  int32 ret = ::send(sock->Fd(), buf->Peek(), len, 0);
  if (ret == -1) {
    PLOG(WARNING)<< "send socker error: " << sock->Fd();
    return -1;
  }

  buf->Skip(ret);
  return ret;
}
}
